Skip to content

fix(comark): make renderMarkdown round-trips safe against injection - #376

Open
atinux wants to merge 9 commits into
mainfrom
fix/roundtrip-safety
Open

fix(comark): make renderMarkdown round-trips safe against injection#376
atinux wants to merge 9 commits into
mainfrom
fix/roundtrip-safety

Conversation

@atinux

@atinux atinux commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

What

Three serializer hardening fixes (one commit each): pickFence() chooses code fences that the content cannot close (pre, mermaid, YAML props blocks), comarkAttributes keeps double quotes inside values inert, and text-node escaping now covers the Comark markers ::/{.

Why

A security scan found that inert content could become live structure after a documented parse → renderMarkdown → re-parse cycle: code containing a ~~~ line or mermaid content containing ``` broke out of its fence into live ::alert markup, an attribute value with a double quote injected new attributes on re-parse, and text holding `::alert{...}` (from escaped or entity-encoded sources) re-parsed into a real component invocation. The quoting fix prefers single quotes because the props parser does not unescape backslashes.

Known limitation: block math ($$…$$) has no alternative delimiter, so math content containing $$ still cannot round-trip — out of scope here.


🤖 Prepared by an AI agent (OpenCode) from a security-audit findings list; commits are signed by the repository owner's key.

atinux added 4 commits August 21, 2026 11:43
pre, mermaid, and YAML attribute blocks picked a fixed 3-backtick fence
(switching to tildes only when the content contained backticks). Code
containing a ~~~ line closed the fence early on re-parse, turning inert
code into live document structure (e.g. ::alert components) — and the
same applied to mermaid content and YAML props blocks.

Add pickFence(): scan the content for the longest line-start run of
each fence character and emit the shorter side, one character longer
than any run present. Also stop growing mermaid content by a blank
line per round trip.
comarkAttributes emitted string values as key="value" with no
escaping, so a value containing a double quote closed the attribute on
re-parse and the remainder became new attacker-chosen attributes (or a
binding resolving frontmatter data). The props parser does not unescape
backslashes, so prefer single quotes when the value has none; fall back
to \"-escaping when both quote kinds are present.
A text node holding ::alert{...} (from an escaped or entity-encoded
source) was emitted verbatim by renderMarkdown, so a re-parse turned
literal text into a live component invocation; a bare :: line inside a
block component also closed the fence early.

escapeLeadingBlock now escapes lines starting with ':', and
escapeInline escapes ':' that can start an inline component (after
whitespace/start/*/_/[, before a name character) and '{' that opens an
attribute block.
@atinux
atinux requested a review from farnabaz as a code owner August 21, 2026 09:45
@vercel

vercel Bot commented Aug 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
comark Ready Ready Preview Aug 25, 2026 1:52pm
comark-json-render Ready Ready Preview Aug 25, 2026 1:52pm
comark-nextjs Ready Ready Preview Aug 25, 2026 1:52pm
comark-nuxt Ready Ready Preview Aug 25, 2026 1:52pm
comark-svelte Ready Ready Preview Aug 25, 2026 1:52pm
comark-sveltekit Ready Ready Preview Aug 25, 2026 1:52pm
comark-twoslash Ready Ready Preview Aug 25, 2026 1:52pm
comark-vue Ready Ready Preview Aug 25, 2026 1:52pm

@pkg-pr-new

pkg-pr-new Bot commented Aug 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

comark

npm i https://pkg.pr.new/comark@376

@comark/angular

npm i https://pkg.pr.new/@comark/angular@376

@comark/ansi

npm i https://pkg.pr.new/@comark/ansi@376

@comark/html

npm i https://pkg.pr.new/@comark/html@376

@comark/nuxt

npm i https://pkg.pr.new/@comark/nuxt@376

@comark/react

npm i https://pkg.pr.new/@comark/react@376

@comark/svelte

npm i https://pkg.pr.new/@comark/svelte@376

@comark/vue

npm i https://pkg.pr.new/@comark/vue@376

commit: f27d920

@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle size snapshot is out of date

The published size of one or more packages changed, so the inline snapshot in
test/bundle.test.ts no longer matches.

Please review the diff below. If the change is expected, accept the new snapshot:

  • 🔄 Update the bundle snapshot — tick this box and CI will run vitest run bundle --update and commit the result to this branch.

Update bundle snapshot

Only maintainers with write access can trigger the update. You can also comment /update-bundle-snapshot, or run it locally with pnpm prepack && pnpm vitest run bundle -u.

Bundle size diff
 FAIL  test/bundle.test.ts > package bundle size > published size of each package
Error: Snapshot `package bundle size > published size of each package 1` mismatched

- Expected
+ Received

@@ -4,7 +4,7 @@
    "@comark/html": "18.9k (58 files)",
    "@comark/nuxt": "11.8k (58 files)",
    "@comark/react": "43.6k (74 files)",
    "@comark/svelte": "43.9k (82 files)",
    "@comark/vue": "60.5k (78 files)",
-   "comark": "408k (156 files)",
+   "comark": "422k (156 files)",
  }

 ❯ test/bundle.test.ts:61:20
     59|     }
     60|
     61|     expect(report).toMatchInlineSnapshot(`
       |                    ^
     62|       {
     63|         "@comark/angular": "54.3k (70 files)",

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

Full CI log

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants